﻿// Copyright (c) 2012 Mokah Nnamezie & Andrew Cooper
// Neo4Net graph database - http://neo4net.codeplex.com
//
// This file is part of Neo4Net - a .Net port of the Neo4j graph database
//
// Neo4Net is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace Neo4Net.Kernel
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using NUnit.Framework;
    using Neo4Net.GraphDb;
    using Neo4Net.GraphDb.Factory;
    using Neo4Net.Helpers.Collection;
    using Neo4Net.Kernel.Impl;

    public class TestShortString
    {
        private const String DefaultShortString = "test";

        private const String DatabaseFilePath = "target/var/qa-shortstring";
        private IGraphDatabaseService db;

        [SetUp]
        public void DoBefore()
        {
            AbstractNeo4NetTestCase.DeleteFileOrDirectory(new FileInfo(DatabaseFilePath));
            db = new GraphDatabaseFactory().NewEmbeddedDatabase(DatabaseFilePath);
        }

        [TearDown]
        public void DoAfter()
        {
            db.Shutdown();
        }

        [Test]
        public void JustToMakeSureThisQaTestSuiteHoldsWater()
        {
            String key = "type";
            String aLongString = "This string should definately not fit in a shortstring";
            long sizeBefore = GetSizeOfStringStore();
            CreateNode(DictionaryUtil.Dictionary(key, aLongString));
            long sizeAfter = GetSizeOfStringStore();
            Assert.IsTrue(sizeAfter > sizeBefore);
        }

        [Test]
        public void MakeSureNoDynamicRecordIsCreatedForShortStringOnEmptyStore()
        {
            MakeSureAShortStringWontGrowStringStore(DefaultShortString);
        }

        [Test]
        public void MakeSureNoDynamicRecordIsCreatedForShortStringOnExistingData()
        {
            long node = CreateNode(DictionaryUtil.Dictionary("name", "Neo"));
            CreateNode(DictionaryUtil.Dictionary("name", "A longer name, not fit for shortstring"));

            MakeSureAShortStringWontGrowStringStore(DefaultShortString);
        }

        [Test]
        public void MakeSureSomeDynamicRecordIsCreatedForStringOnExistingData()
        {
            long node = CreateNode(DictionaryUtil.Dictionary("name", "Neo"));
            CreateNode(DictionaryUtil.Dictionary("name", "A longer name, not fit for shortstring"));

            MakeSureAShortStringWillGrowStringStore("$1\"Ã¼edsa");
        }

        [Test]
        public void MakeSureNumericalShortStringJustBelowLimitWontGrowStringStore()
        {
            MakeSureAShortStringWontGrowStringStore("+123456789 1234");
        }

        [Test]
        public void MakeSureNumericalShortStringJustAboveLimitGrowStringStore()
        {
            MakeSureAShortStringWillGrowStringStore("+123456789 01234");
        }

        [Test]
        public void MakeSureCaseShortStringJustBelowLimitWontGrowStringStore()
        {
            MakeSureAShortStringWontGrowStringStore("SOME_VALUE:T");
        }

        [Test]
        public void MakeSureCaseShortStringJustAboveLimitGrowStringStore()
        {
            MakeSureAShortStringWillGrowStringStore("SOMETHING_LON");
        }

        [Test]
        public void MakeSureAlphaNumericalShortStringJustBelowLimitWontGrowStringStore()
        {
            MakeSureAShortStringWontGrowStringStore("Mattias Yu");
        }

        [Test]
        public void MakeSureAlphaNumericalShortStringJustAboveLimitGrowStringStore()
        {
            MakeSureAShortStringWillGrowStringStore("Mattias Yup");
        }

        [Test]
        public void MakeSureUtf8ShortStringJustBelowLimitWontGrowStringStore()
        {
            MakeSureAShortStringWontGrowStringStore("Â¡@$#abc");
        }

        [Test]
        public void MakeSureUtf8ShortStringJustAboveLimitGrowStringStore()
        {
            MakeSureAShortStringWillGrowStringStore("Â¡@$#abcd");
        }

        [Test]
        public void MakeSureRemoveShortStringWontGrowStringStoreFromShortStringDb()
        {
            CreateNode(DictionaryUtil.Dictionary("key1", "one", "key2", "two"));

            long node = CreateNode(DictionaryUtil.Dictionary("name", "Neo"));
            long sizeBefore = GetSizeOfStringStore();
            RemoveProperty(node, "name");
            Assert.Equals(sizeBefore, GetSizeOfStringStore());
        }

        [Test]
        public void MakeSureRemoveShortStringWontGrowStringStoreFromMixedDb()
        {
            CreateNode(DictionaryUtil.Dictionary("key1", "one", "key2", "A string not fit for shortstring"));

            long node = CreateNode(DictionaryUtil.Dictionary("name", "Neo"));
            long sizeBefore = GetSizeOfStringStore();
            RemoveProperty(node, "name");
            Assert.Equals(sizeBefore, GetSizeOfStringStore());
        }

        [Test]
        public void MakeSureUpdateShortStringWontGrowStringStoreFromShortStringDb()
        {
            CreateNode(DictionaryUtil.Dictionary("key1", "one", "key2", "two"));

            long node = CreateNode(DictionaryUtil.Dictionary("name", "Neo"));
            long sizeBefore = GetSizeOfStringStore();
            SetProperty(node, "name", "new value");
            Assert.Equals(sizeBefore, GetSizeOfStringStore());
        }

        [Test]
        public void MakeSureUpdateShortStringWontGrowStringStoreFromMixedDb()
        {
            CreateNode(DictionaryUtil.Dictionary("key1", "one", "key2", "A string not fit for shortstring"));

            long node = CreateNode(DictionaryUtil.Dictionary("name", "Neo"));
            long sizeBefore = GetSizeOfStringStore();
            SetProperty(node, "name", "new value");
            Assert.Equals(sizeBefore, GetSizeOfStringStore());
        }

        private void RemoveProperty(long node, String key)
        {
            GraphDb.ITransaction tx = db.BeginTx();
            db.GetNodeById(node).RemoveProperty(key);
            tx.Success();
            tx.Finish();
        }

        private void SetProperty(long node, String key, Object value)
        {
            GraphDb.ITransaction tx = db.BeginTx();
            db.GetNodeById(node).SetProperty(key, value);
            tx.Success();
            tx.Finish();
        }

        private void MakeSureAShortStringWontGrowStringStore(String shortString)
        {
            Assert.Equals(0, StringStoreDiff(shortString));
        }

        private void MakeSureAShortStringWillGrowStringStore(String shortString)
        {
            Assert.IsTrue(StringStoreDiff(shortString) > 0);
        }

        private long StringStoreDiff(String propertyValue)
        {
            String key = "type";
            long sizeBefore = GetSizeOfStringStore();
            long node = CreateNode(DictionaryUtil.Dictionary(key, propertyValue));
            long sizeAfter = GetSizeOfStringStore();
            Assert.Equals(propertyValue, db.GetNodeById(node).GetProperty(key));
            return sizeAfter - sizeBefore;
        }

        private long GetSizeOfStringStore()
        {
            db.Shutdown();
            long size = new FileInfo(Path.Combine(DatabaseFilePath, "neostore.propertystore.db.strings")).Length;
            db = new GraphDatabaseFactory().NewEmbeddedDatabase(DatabaseFilePath);
            return size;
        }

        private long CreateNode(IDictionary<String, Object> properties)
        {
            GraphDb.ITransaction tx = db.BeginTx();
            INode node = db.CreateNode();
            foreach (var property in properties)
            {
                node.SetProperty(property.Key, property.Value);
            }
            tx.Success();
            tx.Finish();
            return node.Id;
        }
    }
}
